Studies data Provider
Provider that calculates indicators data and pass the calculated data to the library.
/*** Interface for receiving calculated studies data** When calculating studies, parameters selected by user are transferred to this interface, then calculated studies are received from here** When connecting dxCharts library, developer can implement this interface or use the default implementation [com.devexperts.dxcharts.provider.studies.DxStudiesDataProviderImplementation] and pass it to the library using [DxChartsDataProviders] data class** The [dataFlow] property is used to get calculated studies data. It is a StateFlow that emits a list of [StudiesData] objects representing the studies data.** The [setCandles] method is used to set candles data for studies calculation.** The [setStudiesList] method is used to set a list of studies configurations that has to be calculated.** The [getEnabledStudies] method is used to get a list of studies configurations that has to be calculated.** The [updateTradingSessions] method is used to update trading sessions for studies calculators.*/interface DxChartsStudiesDataProvider {/*** Flow of receiving calculated studies data** This property is a StateFlow that emits a list of [StudiesData] objects. Each [StudiesData] object represents the calculated studies data for a particular study.*/val dataFlow: StateFlow<List<StudiesData>>/*** Sets candles data for studies calculation** This method is used to set the candles data that will be used for calculating the studies. The candles data is represented by a [DxChartsCandles] object.** @param candles The candles data.*/fun setCandles(candles: DxChartsCandles)/*** Sets list of studies configurations that has to be calculated** This method is used to set the list of studies configurations that will be calculated. Each configuration is represented by a [StudiesSetting] object.** @param studies The list of studies configurations.*/fun setStudiesList(studies: List<StudiesSetting>)/*** Gets list of studies configurations that has to be calculated** This method is used to get the list of studies configurations that are currently being calculated. Each configuration is represented by a [StudiesSetting] object.** @return The list of studies configurations.*/fun getEnabledStudies(): List<StudiesSetting>/*** Updates trading sessions for studies calculators** This method is used to update the trading sessions that will be used for calculating the studies. Each trading session is represented by a [TradingSession] object.** @param sessions The list of trading sessions.*/fun updateTradingSessions(sessions: List<TradingSession>)}
Interface methods:
setCandles
: Sets candles data for calculations.setStudiesList
: Sets a list of indicators and their settings.getEnabledStudies
: Returns a list of indicators that are currently being calculated in the provider.updateTradingSessions
: Sets a trading sessions data for calculating indicators.
Data is sent by updating the state variable dataFlow
. It is represented as a list of com.devexperts.dxcharts.provider.StudiesData
objects.
Incoming data is represented as com.devexperts.dxcharts.provider.StudiesSetting
objects.
/*** Data class for storing calculated studies data** @param uuid uuid of the data* @param data DoubleArray with calculated data*/data class StudiesData(val uuid: String = "",val data: Array<DoubleArray?> = emptyArray())/*** Data class representing the settings for a study in a financial indicator.** @property id The unique identifier for the study setting.* @property title The title of the study.* @property uuid The universally unique identifier (UUID) for the study.* @property type The type of the indicator associated with the study.* @property parameters The list of parameters for the study.* @property lines The list of lines for the study.* @property overlaying A boolean indicating whether the study is overlaying on the chart.* @property calculateFutureData A boolean indicating whether to calculate future data for the study.* @property categories A string representing the categories associated with the study.* @property locked A nullable boolean indicating whether the study is locked.*/data class StudiesSetting(val id: String,val title: String,val uuid: String,val type: IndicatorType,val parameters: List<Parameter>,val lines: List<Line>,val overlaying: Boolean,val calculateFutureData: Boolean,val categories: String,val locked: Boolean?) {/*** Data class representing a line associated with a study.** @property title The title of the line.* @property type The type of the study line.* @property thickness The thickness of the line.* @property colors The list of colors associated with the line.* @property visible A boolean indicating whether the line is visible.*/data class Line(val title: String?,val type: Type?,val thickness: Int?,val colors: List<String>?,val visible: Boolean?) {/*** Enum class representing the types of study lines.*/enum class Type {POINTS,LINEAR,HISTOGRAM,DIFFERENCE,ABOVE_CANDLE_TEXT,TEXT,BELOW_CANDLE_TEXT,ABOVE_CANDLE_TRIANGLE,TRIANGLE,COLOR_CANDLE,RECTANGULAR;}}/*** Data class representing a parameter associated with a study.** @property id The unique identifier for the parameter.* @property title The title of the parameter.* @property type The type of the study parameter.* @property value The value of the parameter.* @property validation The [Validation] rules for the parameter.* @property visible A boolean indicating whether the parameter is visible.*/data class Parameter(val id: String,val type: Type,val value: Any?,val validation: Validation?,val visible: Boolean?) {/*** Data class representing validation rules for a study parameter.** @property min The minimum value allowed for the parameter.* @property max The maximum value allowed for the parameter.* @property precision The precision of the parameter value.*/data class Validation(val min: Double?,val max: Double?,val precision: Int?,)/*** Enum class representing the types of study parameters.*/enum class Type {INTEGER_RANGE,DOUBLE_RANGE,PRICE_FIELD,STRING,AGGREGATION,BOOLEAN,AVERAGE,UNDEFINED,}}}/*** Data class representing a trading session with time range and optional high and low values.** [TradingSession] needed for some indicators (e.g. Volume Weighted Average Price)** @property from The starting time of the trading session.* @property to The ending time of the trading session.* @property high The highest value during the trading session (nullable).* @property low The lowest value during the trading session (nullable).*/data class TradingSession(val from: Double,val to: Double,val high: Double? = null,val low: Double? = null)
Calculated indicators looks as follows:

Here is the default implementation of DxChartsStudiesDataProvider
:
/*** Implementation of the [DxChartsStudiesDataProvider] interface, providing functionality* to manage studies data and candles for DX charts.** @property _dataFlow Internal mutable state flow to emit [StudiesData] to the UI.* @property dataFlow Exposed immutable state flow to observe [StudiesData] from the UI.* @property candlesState Internal mutable state flow to manage [DxChartsCandles].* @property studiesListState Internal mutable state flow to manage the list of [StudiesSetting].* @property sessionsFlow Internal mutable state flow to manage [TradingSession]s.* @property executorService Executor service for managing background tasks.* @property job Background job for executing data processing.* @property dxStudies Volatile variable to store the [DxStudies] data.* @property _errorFlow Internal [MutableStateFlow] for sending errors.* @property errorFlow [StateFlow] for sending errors.*/class DxStudiesDataProviderImplementation : DxChartsStudiesDataProvider, DxChartsErrorProvider<StudiesProviderError> {private val _dataFlow: MutableStateFlow<List<StudiesData>> =MutableStateFlow(emptyList())override val dataFlow: StateFlow<List<StudiesData>> get() = _dataFlowprivate val candlesState: MutableStateFlow<DxChartsCandles> =MutableStateFlow(DxChartsCandles())private val studiesListState: MutableStateFlow<List<StudiesSetting>> =MutableStateFlow(emptyList())private val sessionsFlow: MutableStateFlow<List<TradingSession>> = MutableStateFlow(emptyList())private val _errorFlow = MutableStateFlow<StudiesProviderError?>(null)override val errorFlow: StateFlow<StudiesProviderError?> get() = _errorFlowprivate var executorService: ExecutorService? = nullprivate var job: Job? = null@Volatileprivate var dxStudies: DxStudies<StudiesCandle> = DxStudies(0, emptyArray())/*** Starts the background data processing job.* If a previous job exists, it is canceled before starting a new one.* Inside this method, a background job is executed using the [executorService].** When we get new candles, we update [dxStudies] using [updateDxStudies] function.** When we get instrument we get trading sessions for it, that are necessary for some indicators.We need to provide them to [dxStudies] with [DxStudies.setTradingSessions]** After that we take list of parameters of enabled studies and use [DxStudies.createStudy] with this parametersto create [Study]. Then we use [Study.calculateAll] to calculate studies data and create [StudiesData] with it.** In the end we pass this calculated studies data to [_dataFlow]** The data processing includes combining [DxChartsCandles], [StudiesSetting]s, and [TradingSession]s.* Delta candles are calculated based on the difference between new and old candle data.* Studies and trading session data are updated accordingly, and the result is emitted to the UI* through the [_dataFlow] state flow.*/fun connect() {if (job != null) {job?.cancel()job = null}if (executorService == null || executorService?.isShutdown == true) {executorService = Executors.newFixedThreadPool(1)executorService?.execute {runBlocking {job = launch(Dispatchers.IO) {candlesState.combine(studiesListState,transform = { candles, studies -> Pair(candles, studies) }).combine(sessionsFlow,transform = { pair, sessions -> Pair(pair, sessions) }).collect { pair ->val candles = pair.first.firstval settings = pair.first.secondval sessions = pair.secondtry {if (candles.candlesData.isEmpty()) {updateDxStudies(0, listOf())} else {updateDxStudies(candles.candlesData.size, candles.candlesData)}if (candles.aggregation.isDayOrMore()) {dxStudies.setTradingSessions(candles.candlesData.zipWithNext().map { (candle, nextCandle) ->object : TradingSessionData {override val from: Double =candle.timestamp.toDouble()override val high: Double? = nulloverride val low: Double? = nulloverride val to: Double =nextCandle.timestamp.toDouble()}}.toTypedArray())} else {dxStudies.setTradingSessions(sessions.map { session ->object : TradingSessionData {override val from: Double = session.fromoverride val to: Double = session.tooverride val high: Double? = session.highoverride val low: Double? = session.low}}.toTypedArray())}val studies = settings.map { studiesSetting ->val params = getParams(studiesSetting).toTypedArray()dxStudies.createStudy(studiesSetting.id, params)}.mapIndexed { index, study ->StudiesData(settings[index].uuid, study.calculateAll())}_dataFlow.emit(studies)} catch (e: Exception) {_errorFlow.tryEmit(StudiesProviderError.UnknownError("Failed to process studies data: $e.message", e))}}}}}} else {Log.w(TAG, "ExecutorService is already running.")}}/*** Updates the [dxStudies] with the given size and candle data.** @param size The size of the studies.* @param candles The list of candle data [CandleDO].*/private fun updateDxStudies(size: Int, candles: List<CandleDO>) {dxStudies = DxStudies(size,candles.map { it.toStudiesCandle() }.toTypedArray())}/*** Converts studies configuration parameters to a list of [StudyParam].** @param studiesConfig The studies configuration.* @return List of [StudyParam] representing the configuration parameters.*/private fun getParams(studiesConfig: StudiesSetting): List<StudyParam> {return studiesConfig.parameters.map { it.toStudyParam() }}/*** Stops the background job and shuts down the executor service.*/fun disconnect() {job?.cancel()job = nullexecutorService?.shutdown()executorService = null}/*** Sets the DX chart candles for processing.** @param candles The [DxChartsCandles].*/override fun setCandles(candles: DxChartsCandles) {candlesState.tryEmit(candles)}/*** Sets the list of studies settings for processing.** @param studies The list of [StudiesSetting]s.*/override fun setStudiesList(studies: List<StudiesSetting>) {studiesListState.tryEmit(studies)}/*** Retrieves the list of studies settings of enabled indicators.** @return List of [StudiesSetting].*/override fun getEnabledStudies(): List<StudiesSetting> {return studiesListState.value}/*** Updates the trading sessions for processing.** @param sessions The list of [TradingSession]s.*/override fun updateTradingSessions(sessions: List<TradingSession>) {sessionsFlow.tryEmit(sessions)}companion object{private const val TAG = "DxStudiesDataProvider"}}/*** Sealed class representing different types of errors that might occur in the DxStudiesDataProviderImplementation.*/sealed class StudiesProviderError(override val message: String, override val error: Throwable?) : ProviderError {class UnknownError(override val message: String,override val error: Throwable? = null) : StudiesProviderError(message, error)}